package com.xlabs.server.DO;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.User;
import com.xlabs.client.DTO.QuestionDTO;
import com.xlabs.client.DTO.TransferDTO;

/**
 * Question data object.
 */
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class QuestionDO
{

	/**
	 * Primary key.
	 */
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Key key;

	/**
	 * The text comment.
	 */
	@Persistent
	private String questionText;

	/**
	 * The start time of video to which this question relates in seconds.
	 */
	@Persistent
	private Integer startTime;

	/**
	 * The end time of video to which this question relates.
	 */
	@Persistent
	private Integer endTime;

	/**
	 * The video that has this comment.
	 */
	@Persistent
	private Key video;

	/**
	 * Replies to this comment.
	 */
	@Persistent
	private List<Key> replies;

	/**
	 * When created.
	 */
	@Persistent
	private Date createdOn;

	/**
	 * Created by user.
	 */
	@Persistent
	private User createdBy;

	/**
	 * Initializes an instance of TimedComment.
	 * 
	 * @param questionText
	 *            The comment.
	 * @param startTime
	 *            the start of corresponding video interval.
	 * @param endTime
	 *            the end of corresponding video interval.
	 */
	public QuestionDO(String questionText, int startTime, int endTime,
			Date createdOn)
	{
		this.questionText = questionText;
		this.startTime = startTime;
		this.endTime = endTime;
		this.createdOn = createdOn;
	}

	/**
	 * Gets the key.
	 * 
	 * @return id.
	 */
	public Key getKey()
	{
		return key;
	}

	/**
	 * Sets the key.
	 * 
	 * @param key
	 *            key.
	 */
	public void setKey(Key key)
	{
		this.key = key;
	}

	/**
	 * Gets comment.
	 * 
	 * @return comment.
	 */
	public String getQuestionText()
	{
		return questionText;
	}

	/**
	 * Sets Comment.
	 * 
	 * @param questionText
	 *            The text comment.
	 */
	public void setQuestionText(String questionText)
	{
		this.questionText = questionText;
	}

	/**
	 * Gets video.
	 * 
	 * @return video.
	 */
	public Key getVideo()
	{
		return video;
	}

	/**
	 * Sets video
	 * 
	 * @param video
	 *            video that has this comment.
	 */
	public void setVideo(Key video)
	{
		this.video = video;
	}

	/**
	 * Gets replies.
	 * 
	 * @return List of replies.
	 */
	public List<Key> getReplies()
	{
		return replies;
	}

	/**
	 * Sets replies.
	 * 
	 * @param replies
	 *            List of replies.
	 */
	public void setReplies(List<Key> replies)
	{
		this.replies = replies;
	}

	/**
	 * Adds a reply.
	 * 
	 * @param replyKey
	 *            reply key.
	 */
	public void addReply(Key replyKey)
	{
		if (this.replies == null)
		{
			this.replies = new ArrayList<Key>();
		}
		this.replies.add(replyKey);
	}

	/**
	 * Returns the encoded Key of this JDO as a string.
	 * 
	 * @return encoded key.
	 */
	public String getEncodedKey()
	{
		return KeyFactory.keyToString(this.key);
	}

	/**
	 * Returns a range of questions sorted by time
	 * 
	 * @param fromIndexIncl
	 *            The start index of range.
	 * @param toIndexExcl
	 *            The end index of range.
	 * @return result. INEFFICIENT ON APP ENGINE - RUN TIME GROWS LINEAR WITH
	 *         VALUE OF FROMINDEXINCL.
	 */
	@SuppressWarnings("unchecked")
	public static QuestionDTO[] getQuestionsByIndexRange(long fromIndexIncl,
			long toIndexExcl)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			String allQuestionsQuery = "SELECT FROM "
					+ QuestionDO.class.getName() + " ORDER BY startTime ASC ";
			Query qQuery = pm.newQuery(allQuestionsQuery);
			qQuery.setRange(fromIndexIncl, toIndexExcl);
			List<QuestionDO> allQuestions = (List<QuestionDO>) qQuery.execute();
			return QuestionDO.toQuestionDTOArray(allQuestions);
		} finally
		{
			pm.close();
		}
	}

	/**
	 * Returns all questions that fall in (time - length, time + length)
	 * interval.
	 * 
	 * @param time
	 *            center of interval.
	 * @param length
	 *            half of interval's length.
	 * @return all questions that fall in (time - length, time + length)
	 *         interval.
	 */
	@SuppressWarnings("unchecked")
	public static QuestionDTO[] getQuestionsInSymmetricTimeInterval(int time,
			int length)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		try
		{
			String questionsInRangeQuery = "SELECT FROM "
					+ QuestionDO.class.getName()
					+ " where startTime >= :rangeStart && startTime <= :rangeEnd "
					+ " ORDER BY startTime ASC ";
			Query query = pm.newQuery(questionsInRangeQuery);
			List<QuestionDO> questionList = (List<QuestionDO>) query.execute(
					time - length, time + length);

			return QuestionDO.toQuestionDTOArray(questionList);
		} finally
		{
			pm.close();
		}
	}

	/**
	 * Gets a page of questions that are asked in the given time interval.
	 * 
	 * @param startTime
	 *            start time.
	 * @param endTime
	 *            end time.
	 * @param direction
	 *            specifies the direction of query.
	 * @param pageSize
	 *            Size of a page.
	 * @return transfer object including the questions.
	 */
	public static TransferDTO getPagedQuestionsWithStartTimeInInterval(int startTime, int endTime, int direction, int pageSize)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		try
		{
			List<QuestionDO> questionList = queryQuestionPageWithStartTimeInInterval(startTime, endTime, direction, pageSize + 1, pm);
			List<QuestionDTO> dtoList = new ArrayList<QuestionDTO>();
			TransferDTO response = new TransferDTO();

			if (direction == TransferDTO.LEFT_DIRECTION)
			{
				int nStartIdx = (questionList.size() == pageSize + 1) ? 1  : 0;
				dtoList = QuestionDO.toDTOList(questionList.subList(nStartIdx, questionList.size()));
				
				Collections.reverse(dtoList);
				response.addKeyValue(TransferDTO.HAS_PREV_QUESTIONPAGE_KEY, questionList.size() == pageSize + 1);
			}
			else if (direction == TransferDTO.RIGHT_DIRECTION)
			{
				int nEndIdx = (questionList.size() == pageSize + 1) ? pageSize  : questionList.size();
				dtoList = QuestionDO.toDTOList(questionList.subList(0, nEndIdx));
				
				response.addKeyValue(TransferDTO.HAS_NEXT_QUESTIONPAGE_KEY, questionList.size() == pageSize + 1);				
			}

			response.addKeyValue(TransferDTO.QUESTION_KEY, dtoList);

			return response;
		} 
		finally
		{
			pm.close();
		}
	}

	/**
	 * Gets a page of questions that are asked in the given time interval.
	 * 
	 * @param startTimeIncl
	 *            start time.
	 * @param endTimeExcl
	 *            end time.
	 * @param direction
	 *            specifies the direction of query.
	 * @param pageSize
	 *            Size of a page.
	 * @param pm
	 *            persistence manager
	 * @return list of questions.
	 */
	@SuppressWarnings("unchecked")
	public static List<QuestionDO> queryQuestionPageWithStartTimeInInterval(
			int startTimeIncl, 
			int endTimeExcl, 
			int direction, 
			int pageSize,
			PersistenceManager pm)
	{
		String questionsQuery = "SELECT FROM "
				+ QuestionDO.class.getName()
				+ " where startTime >= :intervalStart && startTime < :intervalEnd "
				+ " ORDER BY startTime "
				+ (direction == TransferDTO.RIGHT_DIRECTION ? " ASC "
						: " DESC ");

		Query query = pm.newQuery(questionsQuery);

		query.setRange(0, pageSize);

		List<QuestionDO> questionList = (List<QuestionDO>) query.execute(
				startTimeIncl, endTimeExcl);

		return questionList;
	}

	/**
	 * Gets createdOn.
	 * 
	 * @return time of creation.
	 */
	public Date getCreatedOn()
	{
		return createdOn;
	}

	/**
	 * Sets time of creation.
	 * 
	 * @param createdOn
	 *            time of creation.
	 */
	public void setCreatedOn(Date createdOn)
	{
		this.createdOn = createdOn;
	}

	/**
	 * Gets createdBy.
	 * 
	 * @return creator.
	 */
	public User getCreatedBy()
	{
		return createdBy;
	}

	/**
	 * Sets createdBy.
	 * 
	 * @param createdBy
	 *            creator.
	 */
	public void setCreatedBy(User createdBy)
	{
		this.createdBy = createdBy;
	}

	/**
	 * Gets {@link #startTime}
	 * 
	 * @return {@link #startTime}
	 */
	public Integer getStartTime()
	{
		return startTime;
	}

	/**
	 * Sets {@link #startTime}
	 * 
	 * @param startTime
	 *            {@link #startTime}
	 */
	public void setStartTime(Integer startTime)
	{
		this.startTime = startTime;
	}

	/**
	 * Gets {@link #endTime}
	 * 
	 * @return {@link #endTime}
	 */
	public Integer getEndTime()
	{
		return endTime;
	}

	/**
	 * Sets {@link #endTime}
	 * 
	 * @param endTime
	 *            {@link #endTime}
	 */
	public void setEndTime(Integer endTime)
	{
		this.endTime = endTime;
	}

	/**
	 * Converts an array of {@link com.xlabs.server.DO.QuestionDO} objects to an
	 * array of {@link QuestionDTO}
	 * 
	 * @param questionDOList
	 * @return
	 */
	@Deprecated
	public static QuestionDTO[] toQuestionDTOArray(
			List<QuestionDO> questionDOList)
	{
		QuestionDTO[] questionDTOs = new QuestionDTO[questionDOList.size()];

		int index = 0;
		for (QuestionDO aQuestionDO : questionDOList)
		{
			questionDTOs[index++] = aQuestionDO.toQuestionDTO();
		}

		return questionDTOs;
	}

	public static List<QuestionDTO> toDTOList(List<QuestionDO> doList)
	{
		List<QuestionDTO> dtoList = new ArrayList<QuestionDTO>();

		for (QuestionDO questionDO : doList)
		{
			dtoList.add(questionDO.toQuestionDTO());
		}

		return dtoList;
	}

	/**
	 * Converts this object to CommentDTO.
	 * 
	 * @return com.xlabs.client.CommentDTO
	 */
	public QuestionDTO toQuestionDTO()
	{
		QuestionDTO dto = new QuestionDTO(this.getQuestionText(), this
				.getStartTime(), this.getEndTime(), this.getCreatedOn());

		dto.setEncodedKey(this.getEncodedKey());
		dto.setCreatedBy(this.getCreatedBy().getNickname());

		return dto;
	}
}
